module namespace kart="http://sendmeafilter.com/common/kart";
import module namespace json="http://sendmeafilter.com/common/json" at "json.xqy";
import module namespace kartdata="http://sendmeafilter.com/common/kartdata" at "kartdata.xqy";
import module namespace ck = "http://sendmeafilter.com/modules/cookies" at "cookies.xqy";
import module namespace tx="http://txhelper.org/tx" at "tx.xqy";

declare boundary-space preserve;
declare option xdmp:mapping "false";

declare variable $cookieName := 'smaf';
declare variable $cookieDomain := '.sendmeafilter.com';
declare variable $cookieDomain2 := '.amazonaws.com';
declare variable $cookieExpires := fn:current-dateTime() + xs:dayTimeDuration('P90D');


declare function kart:pricing() { 
	(/xml/pricing, $kartdata:pricing)[1] 
};

declare variable $possible := "BCDFGHJKLMNPQRSTVWXYZ123456789";
declare variable $possLen := fn:string-length($possible); 
declare function kart:makeid($size) {
	fn:string-join( 
		for $i in 1 to $size 
		return fn:substring($possible,1 + (xdmp:random() mod $possLen),1)
		,'')
};

declare function kart:addMonths($date as xs:date, $count as xs:integer, $dayOfMonth as xs:integer?) as xs:date {
  let $date := fn:substring(fn:string($date),1,10)
  let $t := fn:tokenize($date,'-')
  let $y := xs:integer($t[1])
  let $m := xs:integer($t[2]) + $count
  let $d := if($dayOfMonth) then $dayOfMonth else xs:integer($t[3])
  let $y := kart:modYear($y,$m)
  let $m := kart:modMonth($m)
  return kart:makeDate($y,$m,$d)
};

declare function kart:getDayOfMonth() { xs:integer( fn:tokenize(fn:substring(fn:string(fn:current-date()),1,10),'-')) };
declare function kart:modYear($y,$m) { if($m > 12) then kart:modYear($y + 1,$m - 12) else $y };
declare function kart:modMonth($m) { if($m > 12) then kart:modMonth($m - 12) else $m };
declare function kart:makeDate($y,$m,$d) {
  try {
    let $str := fn:string-join((xs:string($y), kart:pad($m), kart:pad($d)),'-')
    return xs:date($str)
  } catch ($x) {
    if($d > 0) then kart:makeDate($y,$m,$d - 1) else xdmp:rethrow()
  }
};

declare function kart:pad($s) { let $s := fn:string($s) return if(fn:string-length($s) = 1) then fn:concat('0',$s) else $s };

declare function kart:getFilterInfo($height as xs:integer, $width as xs:integer, $thickness as xs:integer) {
		if($height gt $width) then
			getFilterInfo2($width,$height,$thickness)
		else
			getFilterInfo2($height,$width,$thickness)
};
	
declare function kart:getFilterInfo2($height,$width,$thickness) {
	let $sqIn := $height * $width
	let $longSide := $width
	let $size := fn:concat($height,'x',$width)
	let $types := ('M8-','M11-','C-')
	let $names := ('Email us to special order this Merv 8 filter size.','Email us to special order this Merv 11 filter size.','Email us to special order this Carbon filter size.')
	let $info := 
		for $t at $i in $types
		let $type := fn:concat($t,$thickness)
		let $default := fn:concat('{"code":"NA","price":"?","text":"',$names[$i], '"}')
		let $matches := 
			for $filter in pricing()/filters/type[@id = $type]/filter
			let $item := 
				if($filter/sizes and fn:contains($filter/sizes,$size)) then
					fn:concat('{"code":"', $filter/@code, '","price":"', $filter/@price, '","text":"',$filter/@name, '"}')
				else 
					for $calc in $filter/size
					let $sideUnder := xs:integer($calc/@sideUnder)
					let $fromSqInches := xs:integer($calc/@fromSqInches)
					let $toSqInches := xs:integer($calc/@toSqInches)
					let $price := xs:float($filter/@price)
					return
						if($longSide le $sideUnder and $sqIn ge $fromSqInches and $sqIn le $toSqInches) then
							fn:concat('{"code":"', $filter/@code, '","price":"', $price, '","text":"',$filter/@name, '"}')
						else ()
			return $item[1]
		return ($matches, $default)[1]			
	return fn:concat('{"selections":[',fn:string-join($info,','),']}')
};

declare function kart:newOrderId() {
	let $newId := kart:makeid(10)
	let $order := /order[id = $newId]
	return if($order) then kart:newOrderId() else $newId
};

declare function kart:makeOrderUri($id) {
	fn:string-join((
		'/orders/',
		fn:string-join(for $i in 0 to 2 return fn:substring($id,1 + ($i * 2),2), '/'),
		'/',$id,'/order-',$id,'.xml'
	),'')
};

declare function kart:getOrder() {
	let $id := ck:get-cookie($cookieName)
	let $order := /order[orderid = $id]
	let $order := 
		if($order) then 
			$order 
		else 
			kartdata:emptyorder(kart:newOrderId())
	let $setCookie := ck:add-cookie($cookieName, $order/orderid/text(),$cookieExpires, $cookieDomain, '/', fn:false(), fn:false())
	let $setCookie := ck:add-cookie($cookieName, $order/orderid/text(),$cookieExpires, $cookieDomain2, '/', fn:false(), fn:false())
	let $setCookie := ck:add-cookie($cookieName, $order/orderid/text(),$cookieExpires, '.localhost', '/', fn:false(), fn:false()) 
	return $order
};

declare function kart:saveOrder($order) {
	let $uri := kart:makeOrderUri($order/orderid/text())
	let $save := xdmp:document-insert($uri,$order)
	return $order
};

declare function kart:putKart() {
	let $json := xdmp:get-request-field('json','{ bad: false }')
	let $xml := json:json2dxml($json)
	let $action := $xml[1]
	let $kart := $xml[2]
	let $order := tx:docs(kart:getOrder())
	let $order := if($action/actionType/text() = 'get') then () else tx:node-replace($order/kart,$kart)
	let $order := tx:docs()/order
	let $order :=
		typeswitch(element {$action/actionType/text()} {}) 
			case element(get) return $order
			case element(recalc) return $order
			case element(add) return kart:add()
			case element(remove) return kart:remove($action)
			case element(validateOffer) return $order
			default return $order
	let $order := kart:recalc()
	let $order := kart:saveOrder($order)
	return json:dxml2json($order/kart,fn:true(),"lineItem")
};

declare function kart:add() {
	let $order := tx:docs()/order
	let $form := $order/kart/addItemForm
	let $code := $form/code/text()
	let $price := kart:pricing()//filter[@code = $code]/@price/fn:data(.)
	let $add :=  tx:node-insert-child($order,
		<item id="{kart:makeid(10)}" qty="{$form/qty}" height="{$form/height}" width="{$form/width}" thickness="{$form/thickness}" price="{$price}">
			<code>{$code}</code>
			<search>{fn:concat($form/height,'x',$form/width)} {fn:concat($form/height,'x',$form/width,'x',$form/thickness)}</search>
		</item>
	)
	return tx:docs()/order
};

declare function kart:remove($action) {
	let $order := tx:docs()/order
	let $remove := $order/item[@id = $action/id/text()]
	let $remove := if($remove) then tx:node-delete($remove) else ()
	return tx:docs()/order
};

(:A1266YD04:)

declare function kart:validateOfferCode() {
	let $code := fn:lower-case(tx:docs()/order/kart/offerCode/text())
	let $removeCode := tx:node-replace-or-insert-child(tx:docs()/order/kart/offerCode,tx:docs()/order/kart,<offerCode/>)
	return 
		if($code) then
			if($code = 'clear') then 
				let $clear := tx:node-delete(tx:docs()/order/discount)
				return ()
			else 
				let $dt := xs:string(fn:current-date())
				let $cyr := fn:substring($dt,3,2)
				let $cmonth := fn:substring($dt,6,2)
				let $init := fn:substring($code,1,1)
				let $yr := fn:substring($code,2,2)
				let $month := fn:substring($code,8,2)
				let $discType := if(fn:substring($code,6,1) = 'z') then 'residual' else 'one time'
				let $percent := fn:substring($code,7,1)
				let $percent := 
					if($percent = 'b') then 5
					else if($percent = 'c') then 10
					else if($percent = 'd') then 20
					else 0
				let $affiliate := fn:substring($code,4,2)
				return 
					if($init = 'a' and $yr = $cyr and $month = $cmonth and $percent > 0) then
						let $disc := 
							<discount percent="{$percent}" type="{$discType}" affiliate="{$affiliate}" code="{$code}"/>
						let $save := tx:node-replace-or-insert-child(tx:docs()/order/discount,tx:docs()/order,$disc)
						return $disc
					else tx:docs()/order/discount
		else tx:docs()/order/discount
};

declare function kart:addItem($id,$class,$qty,$desc,$price,$extprice) {
	let $add := tx:node-insert-child(tx:docs()/order/kart,
		<lineItem>
			{ if($class = 'kart-total-title') then <spanall>true</spanall> else ()}
			{ if($id) then <id>{$id}</id> else () }
			{ if($class) then <class>{$class}</class> else () }
			{ if($qty) then <qty>{$qty}</qty> else () }
			{ if($desc) then <desc>{$desc}</desc> else () }
			{ if($price) then <price>{$price}</price> else () }
			{ if($extprice) then <extprice>{$extprice}</extprice> else () }
		</lineItem>
	)
	return ()
};

declare function kart:makeAttr($name,$value as xs:string) {
	if($value) then attribute {$name} {$value} else ()
};

declare function kart:makeElem($name,$value as xs:string) {
	if($value) then element {$name} {$value} else ()
};

declare function kart:totalSection($elementName, $firstShipment, $subTotal) {
	let $subsequentShipment := fn:not($firstShipment)
	let $computedDiscount := tx:docs()/order/discount
	let $discountIsResidual := $computedDiscount/@type = 'residual'
	let $discountPercent := if($computedDiscount and ($firstShipment or $discountIsResidual)) then xs:float($computedDiscount/@percent) else 0.0
	let $discountAmount := 
		if($computedDiscount) then 
			 -1.0 * fn:round-half-to-even($subTotal * ($discountPercent div  100.0),2)
		else 0.0
	let $discountedSubTotal := fn:round-half-to-even($subTotal + $discountAmount,2)
	let $taxState :=  tx:docs()/order/kart/state/text()
	let $taxInfo := kart:pricing()/taxmap/tax[@state = $taxState]
	let $taxPercent := if($taxInfo) then xs:float($taxInfo/@percent) else 0.0
	let $taxAmount := fn:round-half-to-even($discountedSubTotal * ($taxPercent div 100.0),2)
	let $total := fn:round-half-to-even($discountedSubTotal + $taxAmount,2)
	let $frequency := xs:integer(tx:docs()/order/kart/frequency/text())
	let $start := xs:integer(tx:docs()/order/kart/starting/text())
	let $billingDate := 
		if($firstShipment) then 
			fn:current-date()
		else 
			kart:addMonths(fn:current-date(), $start + 1,())
	let $title :=
		if($firstShipment) then 
			if($start > 0) then
				kart:addMonths(fn:current-date(), $start + 1,())
			else
				fn:current-date()
		else 
			kart:addMonths(fn:current-date(), $start + 1,())
	let $element := element {$elementName} {
		attribute subtotal { $subTotal },
		attribute discountPercent { $discountPercent },
		attribute discountAmount { $discountAmount },
		attribute discountedSubTotal { $discountedSubTotal },
		attribute taxState { $taxState },
		attribute taxPercent { $taxPercent },
		attribute taxAmount { $taxAmount },
		attribute total { $total },
		attribute billingDate { $billingDate },
		attribute title { fn:format-date($billingDate,"[MNn] [D], [Y]") }
	}
	let $replace := xdmp:value(fn:concat("tx:node-replace-or-insert-child(tx:docs()/order/",$elementName,",tx:docs()/order,$element)"))
	return $element
};

declare function kart:renderSection($title, $elementName, $firstShipment, $subTotal) {
	let $totals := kart:totalSection($elementName,$firstShipment,$subTotal)
(:	let $title := :)
	return 
	(
		kart:addItem((),'kart-item',(),'&nbsp;',(),()),
		kart:addItem(fn:replace($title,'@',$totals/@title),'kart-total-title',(),(),(),()),
		if(xs:float($totals/@discountAmount/fn:data(.)) ne 0.0 or xs:float($totals/@taxAmount/fn:data(.)) gt 0.0) then 
			kart:addItem((),'kart-totals',(),'Subtotal:',(),$subTotal)
		else (),
		if(xs:float($totals/@discountAmount/fn:data(.)) ne 0.0) then
			(
				kart:addItem((),'kart-discount',(),fn:concat('Special offer discount (',$totals/@discountPercent/fn:data(.),'%)'),(),$totals/@discountAmount/fn:data(.)),
				if(xs:float($totals/@taxAmount/fn:data(.)) gt 0.0) then
					kart:addItem((),'kart-totals',(),'Discounted Subtotal:',(),$totals/@discountedSubTotal/fn:data(.))
				else ()
			)
		else (),
		if(xs:float($totals/@taxAmount/fn:data(.)) gt 0.0) then
			kart:addItem((),'kart-totals',(),
				fn:concat($totals/@taxState,' state sales tax (',$totals/@taxPercent,'%)'),
				(),$totals/@taxAmount/fn:data(.))
		else (),
		kart:addItem((),'kart-final-total',(),'Total',(),$totals/@total/fn:data(.))
	)
};

declare function kart:recalc() {
	let $removeItems := tx:node-delete(tx:docs()/order/kart/lineItem)
	let $order := tx:docs()/order
	let $filterCount := 0
	let $subTotal := 0
	let $items := 
		for $i in $order/item
		let $qty := xs:integer(fn:data($i/@qty))
		let $price := fn:data($i/@price)
		let $extprice := xs:float($i/@qty) * xs:float($i/@price)
		let $compute := xdmp:set($filterCount,$filterCount + $qty)
		let $compute := xdmp:set($subTotal,$subTotal + xs:float($extprice))
		let $desc := fn:concat($i/@height,'x',$i/@width,'x',$i/@thickness,' ',kart:pricing()/filters//filter[@code = $i/code]/@name/fn:data(.))
		return kart:addItem(fn:data($i/@id),'kart-item',$qty,$desc,$price,$extprice)
	let $computeIfThereAreOrderItems := 
		if($order/item) then
			let $addMultiDiscount :=
				if($filterCount > 1) then
					let $mfdqty := $filterCount - 1
					let $price := xs:float(pricing()/multiFilterDiscount/@amount/fn:data(.))
					let $perExtraFilter := 'true' eq pricing()/multiFilterDiscount/@perAdditional/fn:data(.)
					let $extprice := if($perExtraFilter) then $mfdqty * $price else $price
					let $setSubTotal := xdmp:set($subTotal,$subTotal + $extprice)
					let $order := tx:docs()/order
					let $replaceMFD := tx:node-replace-or-insert-child($order/mfd,$order,element mfd { attribute extprice {$extprice}})
					return kart:addItem((),'kart-discount',if($perExtraFilter) then $mfdqty else (),'Multi-filter discount',$price,$extprice)
				else ()
			let $computedDiscount := kart:validateOfferCode()
			let $title := 
				if(tx:docs()/order/kart/plan/text() = 'monthly') then 
(:					if($computedDiscount/@type = 'one time') then:)
						'Payment for first shipment (billed now)'
(:					else :)
(:						'Payment for first shipment (billed now)':)
				else 
(:					if($computedDiscount/@type = 'one time') then:)
(:						'Payment for first shipment (billed now)':)
(:					else :)
						'Payment per shipment: (first payment now, then at each shipment starting @)'
			let $doFirstTotal := kart:renderSection($title, 'shippingTotals',fn:true(), $subTotal)
			let $frequency := xs:integer(tx:docs()/order/kart/frequency/text())
			let $start := xs:integer(tx:docs()/order/kart/starting/text())
			let $starting := fn:format-date(kart:addMonths(fn:current-date(), $start + 1,()),"[MNn] [D], [Y]")
			let $subTotal := 
				if(tx:docs()/order/kart/plan/text() = 'monthly') then
					 fn:round-half-to-even($subTotal div $frequency,2)
				else $subTotal
			let $addSecond :=
				if(tx:docs()/order/kart/plan/text() = 'monthly' or $computedDiscount/@type = 'one time') then
					let $title :=
						if(tx:docs()/order/kart/plan/text() = 'monthly') then 
							fn:concat('Monthly payments thereafter (beginning ',$starting,')')
						else
							fn:concat('Payment for shipments thereafter (beginning ',$starting,')')
					let $doSecondTotal := kart:renderSection($title, 'nextPayment', fn:false(), $subTotal)	
					return ()
				else ()
			return ()
		else ()
	return tx:docs()/order
};

